Decompiled source of CMRLib v1.0.0

CMRLib.dll

Decompiled 2 hours ago
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using BepInEx;
using BepInEx.Bootstrap;
using BepInEx.Configuration;
using BepInEx.Logging;
using CMRLib.Core;
using ComputerysModdingUtilities;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using TMPro;
using UnityEngine;
using UnityEngine.SceneManagement;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: StraftatMod(true)]
[assembly: TargetFramework(".NETFramework,Version=v4.7.2", FrameworkDisplayName = ".NET Framework 4.7.2")]
[assembly: AssemblyCompany("CMRLib")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: AssemblyInformationalVersion("1.0.0+035ee42632884b38ea937935eb423d7c7e886381")]
[assembly: AssemblyProduct("CMRLib")]
[assembly: AssemblyTitle("CMRLib")]
[assembly: AssemblyVersion("1.0.0.0")]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[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 CMRLib
{
	[BepInPlugin("cmr.core", "CMRLib", "1.0.0")]
	[BepInDependency(/*Could not decode attribute arguments.*/)]
	public sealed class CMRLibPlugin : BaseUnityPlugin
	{
		private void Awake()
		{
			((BaseUnityPlugin)this).Logger.LogInfo((object)"CMRLib 1.0.0 - starting");
			Install("host", delegate
			{
				CmrHost.Install(((BaseUnityPlugin)this).Logger);
			});
			Install("text patch", delegate
			{
				TextPatch.Install(((BaseUnityPlugin)this).Logger);
			});
			Install("Mod menu card (optional)", delegate
			{
				ModMenuDecoration.Apply(typeof(CMRLibPlugin), "CMRLib.Resources.icon.png", "Shared runtime for the CMR mods. Does nothing on its own.", ((BaseUnityPlugin)this).Logger);
			});
			((BaseUnityPlugin)this).Logger.LogInfo((object)"CMRLib 1.0.0 - ready");
		}

		private void Install(string name, Action install)
		{
			try
			{
				install();
				((BaseUnityPlugin)this).Logger.LogInfo((object)("[Init] " + name + ": installed"));
			}
			catch (Exception ex)
			{
				((BaseUnityPlugin)this).Logger.LogError((object)("[Init] " + name + ": NOT installed - " + ex.GetType().Name + ": " + ex.Message));
			}
		}
	}
}
namespace CMRLib.Core
{
	public sealed class CmrHost : MonoBehaviour
	{
		private static GameObject _go;

		private static ManualLogSource _log;

		private static bool _installed;

		public static event Action Tick;

		public static event Action GuiDraw;

		public static void Install(ManualLogSource log)
		{
			_log = log;
			if (!_installed)
			{
				_installed = true;
				SceneManager.sceneLoaded += delegate
				{
					Ensure();
				};
				Ensure();
			}
		}

		private static void Ensure()
		{
			//IL_0013: Unknown result type (might be due to invalid IL or missing references)
			//IL_001d: Expected O, but got Unknown
			if (!((Object)(object)_go != (Object)null))
			{
				_go = new GameObject("CMR_Host");
				Object.DontDestroyOnLoad((Object)(object)_go);
				_go.AddComponent<CmrHost>();
				_log.LogInfo((object)"[Host] CMR_Host created");
			}
		}

		private void Update()
		{
			Action tick = CmrHost.Tick;
			if (tick == null)
			{
				return;
			}
			try
			{
				tick();
			}
			catch (Exception ex)
			{
				_log.LogError((object)("[Host] Tick failed: " + ex.GetType().Name + ": " + ex.Message));
			}
		}

		private void OnGUI()
		{
			Action guiDraw = CmrHost.GuiDraw;
			if (guiDraw == null)
			{
				return;
			}
			try
			{
				guiDraw();
			}
			catch (Exception ex)
			{
				_log.LogError((object)("[Host] GuiDraw failed: " + ex.GetType().Name + ": " + ex.Message));
			}
		}

		private void OnDestroy()
		{
			if (_log != null)
			{
				_log.LogInfo((object)"[Host] CMR_Host destroyed - will be recreated on the next scene");
			}
			_go = null;
		}
	}
	public static class EmbeddedAudio
	{
		private struct WavInfo
		{
			public int FormatTag;

			public int Channels;

			public int SampleRate;

			public int Bits;

			public int DataOffset;

			public int DataLength;
		}

		private static readonly Dictionary<(string Asm, string Res), AudioClip> _cache = new Dictionary<(string, string), AudioClip>();

		public static AudioClip Load(Assembly owner, string resourceName, ManualLogSource log)
		{
			if (owner == null)
			{
				throw new ArgumentNullException("owner");
			}
			if (string.IsNullOrEmpty(resourceName))
			{
				throw new ArgumentNullException("resourceName");
			}
			(string, string) key = (owner.GetName().Name, resourceName);
			if (_cache.TryGetValue(key, out var value))
			{
				return value;
			}
			AudioClip val = null;
			try
			{
				val = LoadInternal(owner, resourceName, log);
			}
			catch (Exception ex)
			{
				log.LogError((object)("[Audio] " + owner.GetName().Name + ":" + resourceName + ": " + ex.GetType().Name + ": " + ex.Message));
			}
			_cache[key] = val;
			return val;
		}

		private static AudioClip LoadInternal(Assembly asm, string resourceName, ManualLogSource log)
		{
			byte[] array;
			using (Stream stream = asm.GetManifestResourceStream(resourceName))
			{
				if (stream == null)
				{
					log.LogError((object)("[Audio] resource not found: " + resourceName + ". Available: " + string.Join(", ", asm.GetManifestResourceNames())));
					return null;
				}
				array = new byte[stream.Length];
				int num;
				for (int i = 0; i < array.Length; i += num)
				{
					num = stream.Read(array, i, array.Length - i);
					if (num <= 0)
					{
						break;
					}
				}
			}
			if (!TryParseWav(array, out var info, out var error))
			{
				log.LogError((object)("[Audio] " + resourceName + ": " + error));
				return null;
			}
			float[] array2 = Decode(array, info);
			if (array2 == null)
			{
				log.LogError((object)$"[Audio] {resourceName}: unsupported format, {info.Bits}-bit (tag {info.FormatTag})");
				return null;
			}
			int num2 = array2.Length / Math.Max(1, info.Channels);
			AudioClip val = AudioClip.Create(Path.GetFileNameWithoutExtension(resourceName), num2, info.Channels, info.SampleRate, false);
			val.SetData(array2, 0);
			log.LogInfo((object)($"[Audio] {resourceName}: {info.Channels}ch {info.SampleRate}Hz {info.Bits}bit, " + $"{val.length:0.###}s"));
			return val;
		}

		private static bool TryParseWav(byte[] b, out WavInfo info, out string error)
		{
			info = default(WavInfo);
			error = null;
			if (b == null || b.Length < 12)
			{
				error = "file shorter than 12 bytes";
				return false;
			}
			if (Tag(b, 0) != "RIFF")
			{
				error = "no RIFF signature";
				return false;
			}
			if (Tag(b, 8) != "WAVE")
			{
				error = "not a WAVE file";
				return false;
			}
			bool flag = false;
			bool flag2 = false;
			int num = 12;
			while (num + 8 <= b.Length)
			{
				string text = Tag(b, num);
				int num2 = BitConverter.ToInt32(b, num + 4);
				int num3 = num + 8;
				if (num2 < 0 || num3 + num2 > b.Length)
				{
					num2 = b.Length - num3;
				}
				if (text == "fmt " && num2 >= 16)
				{
					info.FormatTag = BitConverter.ToInt16(b, num3);
					info.Channels = BitConverter.ToInt16(b, num3 + 2);
					info.SampleRate = BitConverter.ToInt32(b, num3 + 4);
					info.Bits = BitConverter.ToInt16(b, num3 + 14);
					flag = true;
				}
				else if (text == "data")
				{
					info.DataOffset = num3;
					info.DataLength = num2;
					flag2 = true;
				}
				num = num3 + num2 + (num2 & 1);
			}
			if (!flag)
			{
				error = "no fmt chunk";
				return false;
			}
			if (!flag2)
			{
				error = "no data chunk";
				return false;
			}
			if (info.Channels <= 0 || info.SampleRate <= 0)
			{
				error = $"bad parameters: {info.Channels}ch {info.SampleRate}Hz";
				return false;
			}
			return true;
		}

		private static float[] Decode(byte[] b, WavInfo i)
		{
			int dataOffset = i.DataOffset;
			int dataLength = i.DataLength;
			switch (i.Bits)
			{
			case 8:
			{
				float[] array2 = new float[dataLength];
				for (int l = 0; l < dataLength; l++)
				{
					array2[l] = (float)(b[dataOffset + l] - 128) / 128f;
				}
				return array2;
			}
			case 16:
			{
				int num2 = dataLength / 2;
				float[] array3 = new float[num2];
				for (int m = 0; m < num2; m++)
				{
					array3[m] = (float)BitConverter.ToInt16(b, dataOffset + m * 2) / 32768f;
				}
				return array3;
			}
			case 24:
			{
				int num3 = dataLength / 3;
				float[] array4 = new float[num3];
				for (int n = 0; n < num3; n++)
				{
					int num4 = dataOffset + n * 3;
					int num5 = b[num4] | (b[num4 + 1] << 8) | (b[num4 + 2] << 16);
					if ((num5 & 0x800000) != 0)
					{
						num5 |= -16777216;
					}
					array4[n] = (float)num5 / 8388608f;
				}
				return array4;
			}
			case 32:
			{
				int num = dataLength / 4;
				float[] array = new float[num];
				if (i.FormatTag == 3)
				{
					for (int j = 0; j < num; j++)
					{
						array[j] = BitConverter.ToSingle(b, dataOffset + j * 4);
					}
				}
				else
				{
					for (int k = 0; k < num; k++)
					{
						array[k] = (float)BitConverter.ToInt32(b, dataOffset + k * 4) / 2.1474836E+09f;
					}
				}
				return array;
			}
			default:
				return null;
			}
		}

		private static string Tag(byte[] b, int off)
		{
			char c = (char)b[off];
			string text = c.ToString();
			c = (char)b[off + 1];
			string text2 = c.ToString();
			c = (char)b[off + 2];
			string text3 = c.ToString();
			c = (char)b[off + 3];
			return text + text2 + text3 + c;
		}
	}
	public static class FieldSnapshot
	{
		private const BindingFlags Flags = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic;

		private static readonly Dictionary<Type, FieldInfo[]> _cache = new Dictionary<Type, FieldInfo[]>();

		private static bool IsScalar(Type t)
		{
			if (!(t == typeof(float)) && !(t == typeof(int)) && !(t == typeof(bool)) && !(t == typeof(string)) && !(t == typeof(uint)))
			{
				return t == typeof(double);
			}
			return true;
		}

		private static FieldInfo[] ScalarFields(Type t)
		{
			if (_cache.TryGetValue(t, out var value))
			{
				return value;
			}
			List<FieldInfo> list = new List<FieldInfo>();
			FieldInfo[] fields = t.GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
			foreach (FieldInfo fieldInfo in fields)
			{
				if (IsScalar(fieldInfo.FieldType))
				{
					list.Add(fieldInfo);
				}
			}
			FieldInfo[] array = list.ToArray();
			_cache[t] = array;
			return array;
		}

		public static Dictionary<string, object> Take(Component c)
		{
			Dictionary<string, object> dictionary = new Dictionary<string, object>();
			if ((Object)(object)c == (Object)null)
			{
				return dictionary;
			}
			FieldInfo[] array = ScalarFields(((object)c).GetType());
			foreach (FieldInfo fieldInfo in array)
			{
				try
				{
					dictionary[fieldInfo.Name] = fieldInfo.GetValue(c);
				}
				catch
				{
				}
			}
			return dictionary;
		}

		public static (bool Changed, string Text) Diff(Dictionary<string, object> before, Component c)
		{
			if ((Object)(object)c == (Object)null)
			{
				return (Changed: false, Text: "component is gone");
			}
			List<string> list = new List<string>();
			FieldInfo[] array = ScalarFields(((object)c).GetType());
			foreach (FieldInfo fieldInfo in array)
			{
				object value;
				try
				{
					value = fieldInfo.GetValue(c);
				}
				catch
				{
					continue;
				}
				if (before.TryGetValue(fieldInfo.Name, out var value2) && !object.Equals(value2, value))
				{
					list.Add(fieldInfo.Name + ": " + Fmt(value2) + " -> " + Fmt(value));
				}
			}
			if (list.Count != 0)
			{
				return (Changed: true, Text: string.Join("; ", list.ToArray()));
			}
			return (Changed: false, Text: "no changes");
		}

		public static string Fmt(object v)
		{
			if (v != null)
			{
				if (!(v is float num))
				{
					if (!(v is double num2))
					{
						if (!(v is bool))
						{
							if (v is string text)
							{
								return "\"" + text + "\"";
							}
							return Convert.ToString(v, CultureInfo.InvariantCulture);
						}
						if (!(bool)v)
						{
							return "false";
						}
						return "true";
					}
					return num2.ToString("0.#####", CultureInfo.InvariantCulture);
				}
				return num.ToString("0.#####", CultureInfo.InvariantCulture);
			}
			return "null";
		}
	}
	public static class ModMenuDecoration
	{
		private const string ModMenuGuid = "kestrel.straftat.modmenu";

		private const string ApiType = "ModMenu.Api.ModMenuCustomisation";

		public static void Apply(Type ownerType, string iconResource, string description, ManualLogSource log, params ConfigEntryBase[] hide)
		{
			if (ownerType == null)
			{
				throw new ArgumentNullException("ownerType");
			}
			if (!Chainloader.PluginInfos.TryGetValue("kestrel.straftat.modmenu", out var value))
			{
				log.LogInfo((object)"[ModMenu] not installed - settings are available via the console and the .cfg");
				return;
			}
			Type type = AccessTools.TypeByName("ModMenu.Api.ModMenuCustomisation");
			if (type == null)
			{
				log.LogWarning((object)(string.Format("[ModMenu] plugin {0} found but type {1} is missing - ", value.Metadata.Version, "ModMenu.Api.ModMenuCustomisation") + "the API changed, decoration skipped"));
				return;
			}
			log.LogInfo((object)$"[ModMenu] detected {value.Metadata.Version}, settings will appear in the menu automatically");
			TrySetDescription(type, ownerType, description, log);
			if (iconResource != null)
			{
				TrySetIcon(type, ownerType, iconResource, log);
			}
			if (hide != null)
			{
				foreach (ConfigEntryBase entry in hide)
				{
					TryHide(type, entry, log);
				}
			}
		}

		private static bool TryRegister(Type api, string property, string guid, object value, ManualLogSource log)
		{
			PropertyInfo propertyInfo = AccessTools.Property(api, property);
			if (propertyInfo == null)
			{
				log.LogWarning((object)("[ModMenu] " + property + " is absent - the API changed"));
				return false;
			}
			if (!(propertyInfo.GetValue(null, null) is IDictionary dictionary))
			{
				log.LogWarning((object)("[ModMenu] " + property + " is not a dictionary - the API changed"));
				return false;
			}
			dictionary[guid] = value;
			return true;
		}

		private static string GuidOf(Type ownerType)
		{
			return (((MemberInfo)ownerType).GetCustomAttribute<BepInPlugin>() ?? throw new ArgumentException(ownerType.Name + " has no [BepInPlugin] - pass the mod's plugin type", "ownerType")).GUID;
		}

		private static void TrySetDescription(Type api, Type ownerType, string description, ManualLogSource log)
		{
			try
			{
				string text = GuidOf(ownerType);
				if (TryRegister(api, "Descriptions", text, description, log))
				{
					log.LogInfo((object)("[ModMenu] description registered for " + text));
				}
			}
			catch (Exception ex)
			{
				log.LogWarning((object)("[ModMenu] description not set: " + ex.GetType().Name + ": " + ex.Message));
			}
		}

		private static void TrySetIcon(Type api, Type ownerType, string iconResource, ManualLogSource log)
		{
			try
			{
				string text = GuidOf(ownerType);
				Sprite val = LoadIcon(ownerType, iconResource, log);
				if (!((Object)(object)val == (Object)null) && TryRegister(api, "Icons", text, val, log))
				{
					log.LogInfo((object)("[ModMenu] icon registered for " + text));
				}
			}
			catch (Exception ex)
			{
				log.LogWarning((object)("[ModMenu] icon not set: " + ex.GetType().Name + ": " + ex.Message));
			}
		}

		private static Sprite LoadIcon(Type ownerType, string iconResource, ManualLogSource log)
		{
			//IL_0083: Unknown result type (might be due to invalid IL or missing references)
			//IL_0089: Expected O, but got Unknown
			//IL_00db: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ea: Unknown result type (might be due to invalid IL or missing references)
			Assembly assembly = ownerType.Assembly;
			byte[] array;
			using (Stream stream = assembly.GetManifestResourceStream(iconResource))
			{
				if (stream == null)
				{
					log.LogWarning((object)("[ModMenu] icon resource " + iconResource + " not found in " + assembly.GetName().Name));
					return null;
				}
				array = new byte[stream.Length];
				int num;
				for (int i = 0; i < array.Length; i += num)
				{
					num = stream.Read(array, i, array.Length - i);
					if (num <= 0)
					{
						break;
					}
				}
			}
			Texture2D val = new Texture2D(2, 2, (TextureFormat)4, false);
			if (!ImageConversion.LoadImage(val, array))
			{
				log.LogWarning((object)"[ModMenu] icon PNG could not be decoded");
				return null;
			}
			((Object)val).hideFlags = (HideFlags)61;
			((Object)val).name = assembly.GetName().Name + "_Icon";
			Sprite obj = Sprite.Create(val, new Rect(0f, 0f, (float)((Texture)val).width, (float)((Texture)val).height), new Vector2(0.5f, 0.5f));
			((Object)obj).hideFlags = (HideFlags)61;
			((Object)obj).name = ((Object)val).name;
			log.LogInfo((object)$"[ModMenu] icon loaded for {assembly.GetName().Name}: {((Texture)val).width}x{((Texture)val).height}");
			return obj;
		}

		private static void TryHide(Type api, ConfigEntryBase entry, ManualLogSource log)
		{
			if (entry == null)
			{
				return;
			}
			try
			{
				MethodInfo methodInfo = null;
				MethodInfo[] methods = api.GetMethods(BindingFlags.Static | BindingFlags.Public);
				foreach (MethodInfo methodInfo2 in methods)
				{
					if (!(methodInfo2.Name != "HideEntry") && methodInfo2.IsGenericMethodDefinition && methodInfo2.GetParameters().Length == 1)
					{
						methodInfo = methodInfo2;
						break;
					}
				}
				if (methodInfo == null)
				{
					MethodInfo methodInfo3 = AccessTools.Method(api, "HideEntry", new Type[1] { typeof(ConfigEntryBase) }, (Type[])null);
					if (methodInfo3 == null)
					{
						log.LogInfo((object)"[ModMenu] HideEntry is absent, skipped");
						return;
					}
					methodInfo3.Invoke(null, new object[1] { entry });
					return;
				}
				Type type = EntryValueType(entry);
				if (type == null)
				{
					log.LogWarning((object)("[ModMenu] could not hide '" + entry.Definition.Key + "': value type is unknown"));
					return;
				}
				methodInfo.MakeGenericMethod(type).Invoke(null, new object[1] { entry });
			}
			catch (Exception ex)
			{
				log.LogWarning((object)("[ModMenu] could not hide '" + entry.Definition.Key + "': " + ex.GetType().Name + ": " + ex.Message));
			}
		}

		private static Type EntryValueType(ConfigEntryBase entry)
		{
			Type type = ((object)entry).GetType();
			while (type != null)
			{
				if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(ConfigEntry<>))
				{
					return type.GetGenericArguments()[0];
				}
				type = type.BaseType;
			}
			return null;
		}
	}
	public sealed class PrefabRef
	{
		public Component Owner;

		public FieldInfo Field;

		public Component Value;

		public GameObject TargetRoot;

		public override string ToString()
		{
			return $"{((object)Owner).GetType().Name}.{Field.Name} -> {((Object)TargetRoot).name}#{((Object)TargetRoot).GetInstanceID()}";
		}
	}
	public static class PrefabRebinder
	{
		private const BindingFlags Flags = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic;

		public static List<PrefabRef> FindExternalRefs(GameObject root)
		{
			//IL_00af: 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)
			List<PrefabRef> list = new List<PrefabRef>();
			if ((Object)(object)root == (Object)null)
			{
				return list;
			}
			Component[] componentsInChildren = root.GetComponentsInChildren<Component>(true);
			foreach (Component val in componentsInChildren)
			{
				if ((Object)(object)val == (Object)null)
				{
					continue;
				}
				Type type = ((object)val).GetType();
				FieldInfo[] fields;
				try
				{
					fields = type.GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
				}
				catch
				{
					continue;
				}
				FieldInfo[] array = fields;
				foreach (FieldInfo fieldInfo in array)
				{
					if (!typeof(Component).IsAssignableFrom(fieldInfo.FieldType))
					{
						continue;
					}
					Component val2;
					try
					{
						object? value = fieldInfo.GetValue(val);
						val2 = (Component)((value is Component) ? value : null);
					}
					catch
					{
						continue;
					}
					if ((Object)(object)val2 == (Object)null)
					{
						continue;
					}
					GameObject gameObject = ((Component)val2.transform.root).gameObject;
					if (gameObject != root)
					{
						Scene scene = gameObject.scene;
						if (!((Scene)(ref scene)).IsValid())
						{
							list.Add(new PrefabRef
							{
								Owner = val,
								Field = fieldInfo,
								Value = val2,
								TargetRoot = gameObject
							});
						}
					}
				}
			}
			return list;
		}

		public static void ReportSharing(IEnumerable<GameObject> owners, ManualLogSource log)
		{
			Dictionary<int, List<string>> dictionary = new Dictionary<int, List<string>>();
			Dictionary<int, string> dictionary2 = new Dictionary<int, string>();
			foreach (GameObject owner in owners)
			{
				if ((Object)(object)owner == (Object)null)
				{
					continue;
				}
				List<PrefabRef> list = FindExternalRefs(owner);
				log.LogInfo((object)$"[Rebind] {((Object)owner).name}: {list.Count} external references");
				foreach (PrefabRef item in list)
				{
					log.LogInfo((object)$"[Rebind]   {item}");
					int instanceID = ((Object)item.TargetRoot).GetInstanceID();
					dictionary2[instanceID] = ((Object)item.TargetRoot).name;
					if (!dictionary.TryGetValue(instanceID, out var value))
					{
						value = (dictionary[instanceID] = new List<string>());
					}
					if (!value.Contains(((Object)owner).name))
					{
						value.Add(((Object)owner).name);
					}
				}
			}
			foreach (KeyValuePair<int, List<string>> item2 in dictionary)
			{
				if (item2.Value.Count >= 2)
				{
					log.LogWarning((object)($"[Rebind] SHARED prefab {dictionary2[item2.Key]}#{item2.Key} " + "used by: " + string.Join(", ", item2.Value.ToArray())));
				}
			}
		}
	}
	public enum PrefabScope
	{
		Any,
		Pickup,
		Spawned
	}
	public static class PrefabRegistry
	{
		public const string WeaponsResourcePath = "RandomWeapons";

		private static readonly string[] SpawnedMarkerTypes = new string[8] { "PredictedProjectile", "ShrapnelBallistic", "PhysicsGrenade", "ProximityMine", "Claymore", "Obus", "HandGrenadeTwo", "RebondBalle" };

		private static bool _warm;

		private static GameObject[] _pickups = (GameObject[])(object)new GameObject[0];

		private static List<GameObject> _spawned = new List<GameObject>();

		private static ManualLogSource _log;

		private static ManualLogSource Log => _log ?? (_log = Logger.CreateLogSource("CMRLib.Registry"));

		public static IReadOnlyList<GameObject> Pickups
		{
			get
			{
				EnsureWarm();
				return _pickups;
			}
		}

		public static IReadOnlyList<GameObject> Spawned
		{
			get
			{
				EnsureWarm();
				return _spawned;
			}
		}

		public static bool IsWarm => _warm;

		public static void EnsureWarm()
		{
			if (!_warm)
			{
				_pickups = (GameObject[])(((object)Resources.LoadAll<GameObject>("RandomWeapons")) ?? ((object)new GameObject[0]));
				Log.LogInfo((object)string.Format("[Registry] LoadAll(\"{0}\") -> {1} pickups", "RandomWeapons", _pickups.Length));
				if (_pickups.Length == 0)
				{
					Log.LogWarning((object)"[Registry] Empty. Did the Resources path change? Everything below will find nothing.");
				}
				CollectSpawned();
				Log.LogInfo((object)$"[Registry] spawned prefabs: {_spawned.Count}");
				_warm = true;
			}
		}

		private static void CollectSpawned()
		{
			//IL_00fe: Unknown result type (might be due to invalid IL or missing references)
			//IL_0103: Unknown result type (might be due to invalid IL or missing references)
			HashSet<int> hashSet = new HashSet<int>(from p in _pickups
				where (Object)(object)p != (Object)null
				select ((Object)p).GetInstanceID());
			Dictionary<int, GameObject> dictionary = new Dictionary<int, GameObject>();
			string[] spawnedMarkerTypes = SpawnedMarkerTypes;
			foreach (string text in spawnedMarkerTypes)
			{
				Type type = AccessTools.TypeByName(text);
				if (type == null)
				{
					Log.LogWarning((object)("[Registry] type " + text + " not found - did the game update?"));
					continue;
				}
				Object[] array;
				try
				{
					array = Resources.FindObjectsOfTypeAll(type);
				}
				catch (Exception ex)
				{
					Log.LogWarning((object)("[Registry] " + text + ": " + ex.Message));
					continue;
				}
				Object[] array2 = array;
				foreach (Object obj in array2)
				{
					Component val = (Component)(object)((obj is Component) ? obj : null);
					if (val != null)
					{
						GameObject gameObject = ((Component)val.transform.root).gameObject;
						Scene scene = gameObject.scene;
						if (!((Scene)(ref scene)).IsValid() && !hashSet.Contains(((Object)gameObject).GetInstanceID()))
						{
							dictionary[((Object)gameObject).GetInstanceID()] = gameObject;
						}
					}
				}
			}
			_spawned = dictionary.Values.ToList();
		}

		public static GameObject[] Resolve(PrefabQuery query)
		{
			EnsureWarm();
			return (query.Scope switch
			{
				PrefabScope.Pickup => _pickups, 
				PrefabScope.Spawned => _spawned, 
				_ => _pickups.Concat(_spawned), 
			}).Where((GameObject go) => (Object)(object)go != (Object)null && Matches(go, query)).ToArray();
		}

		private static bool Matches(GameObject go, PrefabQuery q)
		{
			if (!string.IsNullOrEmpty(q.Name) && !(q.Exact ? string.Equals(((Object)go).name, q.Name, StringComparison.OrdinalIgnoreCase) : (((Object)go).name.IndexOf(q.Name, StringComparison.OrdinalIgnoreCase) >= 0)))
			{
				return false;
			}
			if (!string.IsNullOrEmpty(q.Layer))
			{
				int num = LayerMask.NameToLayer(q.Layer);
				if (num < 0)
				{
					return false;
				}
				if (go.layer != num)
				{
					return false;
				}
			}
			if (!string.IsNullOrEmpty(q.RequireComponent))
			{
				Type type = AccessTools.TypeByName(q.RequireComponent);
				if (type == null)
				{
					return false;
				}
				if ((Object)(object)go.GetComponentInChildren(type, true) == (Object)null)
				{
					return false;
				}
			}
			return true;
		}
	}
	public sealed class PrefabQuery
	{
		public string Name;

		public bool Exact = true;

		public string Layer;

		public string RequireComponent;

		public PrefabScope Scope;

		public override string ToString()
		{
			List<string> list = new List<string>();
			if (!string.IsNullOrEmpty(Name))
			{
				list.Add(Exact ? ("name==" + Name) : ("name~" + Name));
			}
			if (!string.IsNullOrEmpty(Layer))
			{
				list.Add("layer=" + Layer);
			}
			if (!string.IsNullOrEmpty(RequireComponent))
			{
				list.Add("has=" + RequireComponent);
			}
			if (Scope != PrefabScope.Any)
			{
				list.Add($"scope={Scope}");
			}
			return string.Join(", ", list);
		}
	}
	public sealed class TextPatch
	{
		private const int RetryEveryFrames = 30;

		private static readonly List<TextPatch> _pending = new List<TextPatch>();

		private static ManualLogSource _log;

		private static bool _installed;

		private readonly string _path;

		private Func<string, string> _transform;

		private float? _fontSize;

		private bool _keep;

		private string _applied;

		private string _original;

		private int _lastTry = -999;

		private bool _done;

		private TextPatch(string path)
		{
			_path = path;
		}

		public static void Install(ManualLogSource log)
		{
			_log = log;
			if (!_installed)
			{
				_installed = true;
				CmrHost.Tick += TickAll;
			}
		}

		public static TextPatch At(string path)
		{
			TextPatch textPatch = new TextPatch(path);
			_pending.Add(textPatch);
			return textPatch;
		}

		public TextPatch Set(string text)
		{
			return Transform((string _) => text);
		}

		public TextPatch Append(string suffix)
		{
			return Transform((string orig) => orig + suffix);
		}

		public TextPatch Prepend(string prefix)
		{
			return Transform((string orig) => prefix + orig);
		}

		public TextPatch Transform(Func<string, string> f)
		{
			_transform = f;
			return this;
		}

		public TextPatch FontSize(float size)
		{
			_fontSize = size;
			return this;
		}

		public TextPatch Keep()
		{
			_keep = true;
			return this;
		}

		private static void TickAll()
		{
			for (int num = _pending.Count - 1; num >= 0; num--)
			{
				TextPatch textPatch = _pending[num];
				if (textPatch.TryApply() && textPatch._done)
				{
					_pending.RemoveAt(num);
				}
			}
		}

		private bool TryApply()
		{
			if (Time.frameCount - _lastTry < 30)
			{
				return false;
			}
			_lastTry = Time.frameCount;
			Transform val = UiFind.Path(_path);
			if ((Object)(object)val == (Object)null)
			{
				return false;
			}
			TMP_Text val2 = ((Component)val).GetComponent<TMP_Text>() ?? ((Component)val).GetComponentInChildren<TMP_Text>(true);
			if ((Object)(object)val2 == (Object)null)
			{
				_log.LogWarning((object)("[Text] " + _path + ": object found but it has no TMP_Text"));
				_done = true;
				return true;
			}
			if (_original == null)
			{
				_original = val2.text ?? "";
				_log.LogInfo((object)("[Text] " + _path + ": original = \"" + Escape(_original) + "\""));
			}
			if (_applied != null && val2.text != _applied)
			{
				_original = val2.text ?? "";
				if (_keep)
				{
					_log.LogInfo((object)("[Text] " + _path + ": game overwrote it, restoring ours"));
				}
			}
			string text = ((_transform != null) ? _transform(_original) : _original);
			if (val2.text != text)
			{
				val2.text = text;
				_applied = text;
				_log.LogInfo((object)("[Text] " + _path + ": \"" + Escape(_original) + "\" -> \"" + Escape(text) + "\""));
			}
			else
			{
				_applied = text;
			}
			if (_fontSize.HasValue && Math.Abs(val2.fontSize - _fontSize.Value) > 0.01f)
			{
				val2.fontSize = _fontSize.Value;
				_log.LogInfo((object)$"[Text] {_path}: fontSize -> {_fontSize.Value}");
			}
			if (!_keep)
			{
				_done = true;
			}
			return true;
		}

		private static string Escape(string s)
		{
			if (s != null)
			{
				return s.Replace("\r", "\\r").Replace("\n", "\\n");
			}
			return "<null>";
		}
	}
	public sealed class Tweak
	{
		private sealed class Step
		{
			public string Label;

			public Func<GameObject, StepResult> Run;
		}

		internal readonly struct StepResult
		{
			public readonly bool Changed;

			public readonly string Text;

			public StepResult(bool changed, string text)
			{
				Changed = changed;
				Text = text;
			}
		}

		private readonly List<Step> _steps = new List<Step>();

		public string Id { get; private set; }

		public PrefabQuery Query { get; private set; }

		public bool AllowMultiple { get; private set; }

		public bool Optional { get; private set; }

		public bool IsPinned { get; private set; }

		public string PinReason { get; private set; }

		internal int StepCount => _steps.Count;

		private Tweak(string id, PrefabQuery query)
		{
			Id = id;
			Query = query;
		}

		public static Tweak Pickup(string name)
		{
			return new Tweak(name, new PrefabQuery
			{
				Name = name,
				Exact = true,
				Scope = PrefabScope.Pickup
			});
		}

		public static Tweak Spawned(string name)
		{
			return new Tweak(name, new PrefabQuery
			{
				Name = name,
				Exact = true,
				Scope = PrefabScope.Spawned
			});
		}

		public static Tweak Any(string name)
		{
			return new Tweak(name, new PrefabQuery
			{
				Name = name,
				Exact = true,
				Scope = PrefabScope.Any
			});
		}

		public static Tweak Where(string id, PrefabQuery query)
		{
			return new Tweak(id, query);
		}

		public Tweak OnLayer(string layer)
		{
			Query.Layer = layer;
			return this;
		}

		public Tweak WithComponent(string typeName)
		{
			Query.RequireComponent = typeName;
			return this;
		}

		public Tweak Multiple()
		{
			AllowMultiple = true;
			return this;
		}

		public Tweak IfPresent()
		{
			Optional = true;
			return this;
		}

		public Tweak Pin(string reason = null)
		{
			IsPinned = true;
			PinReason = reason;
			return this;
		}

		public Tweak Weapon(Action<Weapon> apply)
		{
			return this.Component<Weapon>(apply, "Weapon");
		}

		public Tweak Melee(Action<MeleeWeapon> apply)
		{
			return this.Component<MeleeWeapon>(apply, "MeleeWeapon");
		}

		public Tweak Item(Action<ItemBehaviour> apply)
		{
			return this.Component<ItemBehaviour>(apply, "ItemBehaviour");
		}

		public Tweak Rename(string weaponName)
		{
			_steps.Add(new Step
			{
				Label = "Rename -> \"" + weaponName + "\"",
				Run = delegate(GameObject root)
				{
					ItemBehaviour componentInChildren = root.GetComponentInChildren<ItemBehaviour>(true);
					if ((Object)(object)componentInChildren == (Object)null)
					{
						throw new TweakException("no ItemBehaviour");
					}
					string weaponName2 = componentInChildren.weaponName;
					componentInChildren.weaponName = weaponName;
					return (!(weaponName2 == weaponName)) ? new StepResult(changed: true, "weaponName: \"" + weaponName2 + "\" -> \"" + weaponName + "\"") : new StepResult(changed: false, "weaponName: already \"" + weaponName + "\"");
				}
			});
			return this;
		}

		public Tweak Component<T>(Action<T> apply, string label = null) where T : Component
		{
			string name = label ?? typeof(T).Name;
			_steps.Add(new Step
			{
				Label = name,
				Run = delegate(GameObject root)
				{
					T componentInChildren = root.GetComponentInChildren<T>(true);
					if ((Object)(object)componentInChildren == (Object)null)
					{
						throw new TweakException("no " + name);
					}
					Dictionary<string, object> before = FieldSnapshot.Take((Component)(object)componentInChildren);
					apply(componentInChildren);
					var (changed, text) = FieldSnapshot.Diff(before, (Component)(object)componentInChildren);
					return new StepResult(changed, name + " { " + text + " }");
				}
			});
			return this;
		}

		public Tweak Private<T>(string field, object value) where T : Component
		{
			_steps.Add(new Step
			{
				Label = typeof(T).Name + "." + field,
				Run = delegate(GameObject root)
				{
					T componentInChildren = root.GetComponentInChildren<T>(true);
					if ((Object)(object)componentInChildren == (Object)null)
					{
						throw new TweakException("no " + typeof(T).Name);
					}
					return SetField(componentInChildren, typeof(T), field, value);
				}
			});
			return this;
		}

		public Tweak PrivateOn(string typeName, string field, object value)
		{
			_steps.Add(new Step
			{
				Label = typeName + "." + field,
				Run = delegate(GameObject root)
				{
					Type type = AccessTools.TypeByName(typeName);
					if (type == null)
					{
						throw new TweakException("type " + typeName + " not found");
					}
					Component componentInChildren = root.GetComponentInChildren(type, true);
					if ((Object)(object)componentInChildren == (Object)null)
					{
						throw new TweakException("no " + typeName);
					}
					return SetField(componentInChildren, type, field, value);
				}
			});
			return this;
		}

		public Tweak HideChild(string childName)
		{
			_steps.Add(new Step
			{
				Label = "HideChild(" + childName + ")",
				Run = delegate(GameObject root)
				{
					List<string> list = new List<string>();
					int num = 0;
					Transform val = root.transform.Find(childName);
					if ((Object)(object)val != (Object)null)
					{
						if (((Component)val).gameObject.activeSelf)
						{
							((Component)val).gameObject.SetActive(false);
							list.Add(((Object)val).name);
						}
						else
						{
							num++;
						}
					}
					else
					{
						Transform[] componentsInChildren = root.GetComponentsInChildren<Transform>(true);
						foreach (Transform val2 in componentsInChildren)
						{
							if (string.Equals(((Object)val2).name, childName, StringComparison.OrdinalIgnoreCase))
							{
								if (((Component)val2).gameObject.activeSelf)
								{
									((Component)val2).gameObject.SetActive(false);
									list.Add(((Object)val2).name);
								}
								else
								{
									num++;
								}
							}
						}
					}
					if (list.Count == 0 && num == 0)
					{
						throw new TweakException("no child named '" + childName + "'");
					}
					return (list.Count <= 0) ? new StepResult(changed: false, "HideChild(" + childName + "): already hidden") : new StepResult(changed: true, "HideChild(" + childName + "): hidden" + ((list.Count > 1) ? (" " + list.Count) : ""));
				}
			});
			return this;
		}

		public Tweak Raw(string describe, Action<GameObject> action)
		{
			_steps.Add(new Step
			{
				Label = describe,
				Run = delegate(GameObject root)
				{
					action(root);
					return new StepResult(changed: true, describe);
				}
			});
			return this;
		}

		internal int ApplyTo(GameObject root, List<string> applied)
		{
			int num = 0;
			foreach (Step step in _steps)
			{
				StepResult stepResult = step.Run(root);
				applied.Add(stepResult.Text);
				if (stepResult.Changed)
				{
					num++;
				}
			}
			return num;
		}

		private static StepResult SetField(object target, Type declaring, string field, object value)
		{
			FieldInfo fieldInfo = AccessTools.Field(declaring, field);
			if (fieldInfo == null)
			{
				throw new TweakException("no field " + declaring.Name + "." + field);
			}
			object obj = value;
			if (value != null && !fieldInfo.FieldType.IsInstanceOfType(value))
			{
				try
				{
					obj = Convert.ChangeType(value, fieldInfo.FieldType);
				}
				catch
				{
					throw new TweakException(declaring.Name + "." + field + ": " + value?.GetType().Name + " is not convertible to " + fieldInfo.FieldType.Name);
				}
			}
			object value2 = fieldInfo.GetValue(target);
			fieldInfo.SetValue(target, obj);
			string text = declaring.Name + "." + field;
			if (!object.Equals(value2, obj))
			{
				return new StepResult(changed: true, text + ": " + FieldSnapshot.Fmt(value2) + " -> " + FieldSnapshot.Fmt(obj));
			}
			return new StepResult(changed: false, text + ": already " + FieldSnapshot.Fmt(obj));
		}
	}
	public sealed class TweakException : Exception
	{
		public TweakException(string message)
			: base(message)
		{
		}
	}
	public static class TweakRunner
	{
		public sealed class Report
		{
			public int Applied;

			public int Missing;

			public int Ambiguous;

			public int Failed;

			public int NoOp;

			public int Pinned;

			public readonly List<string> Problems = new List<string>();

			public bool AllGood
			{
				get
				{
					if (Missing == 0 && Ambiguous == 0 && Failed == 0)
					{
						return NoOp == 0;
					}
					return false;
				}
			}
		}

		public static Report Run(string groupName, IEnumerable<Tweak> tweaks, ManualLogSource log)
		{
			Report report = new Report();
			IList<Tweak> list = (tweaks as IList<Tweak>) ?? tweaks.ToList();
			log.LogInfo((object)$"[Tweaks] === {groupName}: {list.Count} tweaks ===");
			foreach (Tweak item in list)
			{
				try
				{
					RunOne(item, log, report);
				}
				catch (Exception ex)
				{
					report.Failed++;
					string text = item.Id + ": unexpected failure - " + ex.GetType().Name + ": " + ex.Message;
					report.Problems.Add(text);
					log.LogError((object)("[Tweaks] " + text));
				}
			}
			log.LogInfo((object)($"[Tweaks] === {groupName}: applied {report.Applied}, " + $"pinned-unchanged {report.Pinned}, no-op {report.NoOp}, " + $"missing {report.Missing}, ambiguous {report.Ambiguous}, " + $"failed {report.Failed} ==="));
			return report;
		}

		private static void RunOne(Tweak tweak, ManualLogSource log, Report report)
		{
			GameObject[] array = PrefabRegistry.Resolve(tweak.Query);
			if (array.Length == 0)
			{
				if (!tweak.Optional)
				{
					report.Missing++;
					string text = $"{tweak.Id}: not found ({tweak.Query})";
					report.Problems.Add(text);
					log.LogWarning((object)("[Tweaks] " + text));
				}
				return;
			}
			if (array.Length > 1 && !tweak.AllowMultiple)
			{
				report.Ambiguous++;
				string arg = string.Join(", ", array.Select((GameObject h) => $"{((Object)h).name}#{((Object)h).GetInstanceID()}"));
				string text2 = $"{tweak.Id}: {array.Length} matches ({arg}) - narrow the selector " + "(layer/component) or allow .Multiple()";
				report.Problems.Add(text2);
				log.LogWarning((object)("[Tweaks] " + text2));
				return;
			}
			GameObject[] array2 = array;
			foreach (GameObject val in array2)
			{
				List<string> list = new List<string>();
				try
				{
					int num2 = tweak.ApplyTo(val, list);
					report.Applied++;
					string text3 = string.Join("; ", list);
					if (num2 == 0 && tweak.IsPinned)
					{
						report.Pinned++;
						string text4 = (string.IsNullOrEmpty(tweak.PinReason) ? "" : (" - " + tweak.PinReason));
						log.LogInfo((object)("[Tweaks] " + tweak.Id + " -> " + ((Object)val).name + ": pin holds current value" + text4 + " (" + text3 + ")"));
					}
					else if (num2 == 0)
					{
						report.NoOp++;
						string text5 = tweak.Id + " -> " + ((Object)val).name + ": NOTHING CHANGED (" + text3 + ")";
						report.Problems.Add(text5);
						log.LogWarning((object)("[Tweaks] " + text5));
					}
					else
					{
						log.LogInfo((object)("[Tweaks] " + tweak.Id + " -> " + ((Object)val).name + ": " + text3));
					}
				}
				catch (TweakException ex)
				{
					report.Failed++;
					string text6 = $"{tweak.Id} -> {((Object)val).name}: {ex.Message} (completed {list.Count}/{tweak.StepCount})";
					report.Problems.Add(text6);
					log.LogWarning((object)("[Tweaks] " + text6));
				}
				catch (Exception ex2)
				{
					report.Failed++;
					string text7 = tweak.Id + " -> " + ((Object)val).name + ": " + ex2.GetType().Name + ": " + ex2.Message;
					report.Problems.Add(text7);
					log.LogError((object)("[Tweaks] " + text7));
				}
			}
		}
	}
	public static class UiFind
	{
		public static Transform Root(string name)
		{
			//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)
			for (int i = 0; i < SceneManager.sceneCount; i++)
			{
				Scene sceneAt = SceneManager.GetSceneAt(i);
				if (!((Scene)(ref sceneAt)).isLoaded)
				{
					continue;
				}
				GameObject[] rootGameObjects = ((Scene)(ref sceneAt)).GetRootGameObjects();
				foreach (GameObject val in rootGameObjects)
				{
					if (((Object)val).name == name)
					{
						return val.transform;
					}
				}
			}
			GameObject val2 = GameObject.Find(name);
			if (!((Object)(object)val2 != (Object)null))
			{
				return null;
			}
			return val2.transform;
		}

		public static Transform Descendant(Transform parent, string name)
		{
			if ((Object)(object)parent == (Object)null)
			{
				return null;
			}
			Queue<Transform> queue = new Queue<Transform>();
			queue.Enqueue(parent);
			while (queue.Count > 0)
			{
				Transform val = queue.Dequeue();
				if (((Object)val).name == name)
				{
					return val;
				}
				for (int i = 0; i < val.childCount; i++)
				{
					queue.Enqueue(val.GetChild(i));
				}
			}
			return null;
		}

		public static Transform Path(string path)
		{
			if (string.IsNullOrEmpty(path))
			{
				return null;
			}
			string[] array = path.Split(new char[1] { '/' });
			Transform val = Root(array[0]);
			for (int i = 1; i < array.Length; i++)
			{
				if (!((Object)(object)val != (Object)null))
				{
					break;
				}
				val = Descendant(val, array[i]);
			}
			return val;
		}

		public static string FullPath(Transform t)
		{
			if ((Object)(object)t == (Object)null)
			{
				return "<null>";
			}
			string text = ((Object)t).name;
			Transform parent = t.parent;
			while ((Object)(object)parent != (Object)null)
			{
				text = ((Object)parent).name + "/" + text;
				parent = parent.parent;
			}
			return text;
		}
	}
}